{
  config,
  lib,
  pkgs,
  ...
}:

with lib;

let
  cfg = config.services.supybot;
  isStateDirHome = hasPrefix "/home/" cfg.stateDir;
  isStateDirVar = cfg.stateDir == "/var/lib/supybot";
  pyEnv = pkgs.python3.withPackages (p: [ p.limnoria ] ++ (cfg.extraPackages p));
in
{
  options = {

    services.supybot = {

      enable = mkOption {
        type = types.bool;
        default = false;
        description = "Enable Supybot, an IRC bot (also known as Limnoria).";
      };

      stateDir = mkOption {
        type = types.path;
        default =
          if versionAtLeast config.system.stateVersion "20.09" then "/var/lib/supybot" else "/home/supybot";
        defaultText = literalExpression "/var/lib/supybot";
        description = "The root directory, logs and plugins are stored here";
      };

      configFile = mkOption {
        type = types.path;
        description = ''
          Path to initial supybot config file. This can be generated by
          running supybot-wizard.

          Note: all paths should include the full path to the stateDir
          directory (backup conf data logs logs/plugins plugins tmp web).
        '';
      };

      plugins = mkOption {
        type = types.attrsOf types.path;
        default = { };
        description = ''
          Attribute set of additional plugins that will be symlinked to the
          {file}`plugin` subdirectory.

          Please note that you still need to add the plugins to the config
          file (or with `!load`) using their attribute name.
        '';
        example = literalExpression ''
          let
            plugins = pkgs.fetchzip {
              url = "https://github.com/ProgVal/Supybot-plugins/archive/57c2450c.zip";
              sha256 = "077snf84ibnva3sbpzdfpfma6hcdw7dflwnhg6pw7mgnf0nd84qd";
            };
          in
          {
            Wikipedia = "''${plugins}/Wikipedia";
            Decide = ./supy-decide;
          }
        '';
      };

      extraPackages = mkOption {
        type = types.functionTo (types.listOf types.package);
        default = p: [ ];
        defaultText = literalExpression "p: []";
        description = ''
          Extra Python packages available to supybot plugins. The
          value must be a function which receives the attrset defined
          in {var}`python3Packages` as the sole argument.
        '';
        example = literalExpression "p: [ p.lxml p.requests ]";
      };

    };

  };

  config = mkIf cfg.enable {

    environment.systemPackages = [ pkgs.python3Packages.limnoria ];

    users.users.supybot = {
      uid = config.ids.uids.supybot;
      group = "supybot";
      description = "Supybot IRC bot user";
      home = cfg.stateDir;
      isSystemUser = true;
    };

    users.groups.supybot = {
      gid = config.ids.gids.supybot;
    };

    systemd.services.supybot = {
      description = "Supybot, an IRC bot";
      documentation = [ "https://limnoria.readthedocs.io/" ];
      after = [ "network.target" ];
      wantedBy = [ "multi-user.target" ];
      preStart = ''
        # This needs to be created afresh every time
        rm -f '${cfg.stateDir}/supybot.cfg.bak'
      '';

      startLimitIntervalSec = 5 * 60; # 5 min
      startLimitBurst = 1;
      serviceConfig = {
        ExecStart = "${pyEnv}/bin/supybot ${cfg.stateDir}/supybot.cfg";
        PIDFile = "/run/supybot.pid";
        User = "supybot";
        Group = "supybot";
        UMask = "0007";
        Restart = "on-abort";

        NoNewPrivileges = true;
        PrivateDevices = true;
        PrivateMounts = true;
        PrivateTmp = true;
        ProtectControlGroups = true;
        ProtectKernelModules = true;
        ProtectKernelTunables = true;
        RestrictAddressFamilies = [
          "AF_INET"
          "AF_INET6"
        ];
        RestrictSUIDSGID = true;
        SystemCallArchitectures = "native";
        RestrictNamespaces = true;
        RestrictRealtime = true;
        LockPersonality = true;
        MemoryDenyWriteExecute = true;
        RemoveIPC = true;
        ProtectHostname = true;
        CapabilityBoundingSet = "";
        ProtectSystem = "full";
      }
      // optionalAttrs isStateDirVar {
        StateDirectory = "supybot";
        ProtectSystem = "strict";
      }
      // optionalAttrs (!isStateDirHome) {
        ProtectHome = true;
      };
    };

    systemd.tmpfiles.rules = [
      "d '${cfg.stateDir}'              0700 supybot supybot - -"
      "d '${cfg.stateDir}/backup'       0750 supybot supybot - -"
      "d '${cfg.stateDir}/conf'         0750 supybot supybot - -"
      "d '${cfg.stateDir}/data'         0750 supybot supybot - -"
      "d '${cfg.stateDir}/plugins'      0750 supybot supybot - -"
      "d '${cfg.stateDir}/logs'         0750 supybot supybot - -"
      "d '${cfg.stateDir}/logs/plugins' 0750 supybot supybot - -"
      "d '${cfg.stateDir}/tmp'          0750 supybot supybot - -"
      "d '${cfg.stateDir}/web'          0750 supybot supybot - -"
      "L '${cfg.stateDir}/supybot.cfg'  -    -       -       - ${cfg.configFile}"
    ]
    ++ (flip mapAttrsToList cfg.plugins (
      name: dest: "L+ '${cfg.stateDir}/plugins/${name}' - - - - ${dest}"
    ));

  };
}
